Skip to content

[Win32] Normalise \r and \r\n to \n in GDI+ text rendering#3368

Draft
HeikoKlare wants to merge 1 commit into
eclipse-platform:masterfrom
HeikoKlare:fix/gdip-line-delimiter-normalisation
Draft

[Win32] Normalise \r and \r\n to \n in GDI+ text rendering#3368
HeikoKlare wants to merge 1 commit into
eclipse-platform:masterfrom
HeikoKlare:fix/gdip-line-delimiter-normalisation

Conversation

@HeikoKlare
Copy link
Copy Markdown
Contributor

Summary

GDI+ Graphics_DrawString only treats \n as a line delimiter; bare \r is silently ignored rather than treated as a line break. The GDI DrawText path (non-advanced mode) and the old DrawDriverString path both accept \r and \r\n as line delimiters, so callers that pass Windows-style (CRLF) or old Mac-style (CR-only) line endings with DRAW_DELIMITER set get different results depending on whether advanced mode is active.

  • Fix: in drawTextGDIP(), when DRAW_DELIMITER is active, normalise \r\n\n first (to avoid double line breaks), then convert any remaining lone \r\n before passing the string to Graphics_DrawString.
  • Add a cross-platform regression test (test_textExtentLjava_lang_StringI_lineDelimiterVariants) that verifies all three line-ending variants produce the same textExtent height in both advanced and non-advanced mode.

Contributes to #3091

Manual verification

The following snippet renders the same two-line text with \n, \r, and \r\n in both GDI (reference) and GDI+ mode side by side. Before the fix, the CR column in the GDI+ row shows both lines collapsed onto one line. After the fix all six cells look identical.

The character is included in the GDI+ strings to force the drawTextGDIP() path via useGDIP() (it has no glyph in Courier New). It appears at the end of line 2 and can be ignored.

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class SnippetGdipLineEndings {

    private static final String GDIP_TRIGGER = "中";

    private static final String[][] CASES = {
        { "LF  (\n)",     "Line1\nLine2",   "Line1\nLine2 "   + GDIP_TRIGGER },
        { "CR  (\r)",     "Line1\rLine2",   "Line1\rLine2 "   + GDIP_TRIGGER },
        { "CRLF (\r\n)", "Line1\r\nLine2", "Line1\r\nLine2 " + GDIP_TRIGGER },
    };

    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("GDI+ line-ending test");
        shell.setSize(660, 340);
        shell.setLayout(new FillLayout());

        Canvas canvas = new Canvas(shell, SWT.DOUBLE_BUFFERED);
        Font font = new Font(display, "Courier New", 11, SWT.NORMAL);
        canvas.setFont(font);

        canvas.addPaintListener(e -> {
            GC gc = e.gc;
            gc.setFont(font);
            int colW = 200, rowH = 120, labelH = 18, margin = 10;

            for (int i = 0; i < CASES.length; i++) {
                gc.setAdvanced(false);
                gc.drawText(CASES[i][0], margin + i * colW, 5, SWT.DRAW_TRANSPARENT);
            }

            gc.setAdvanced(false);
            gc.drawText("GDI (reference):", margin, labelH + 8, SWT.DRAW_TRANSPARENT);
            for (int i = 0; i < CASES.length; i++) {
                int x = margin + i * colW, y = labelH + 24;
                gc.setAdvanced(false);
                gc.drawText(CASES[i][1], x, y, SWT.DRAW_DELIMITER | SWT.DRAW_TRANSPARENT);
                gc.drawRectangle(x - 2, y - 2, colW - 16, rowH - labelH - 24);
            }

            int row2Y = rowH + 10;
            gc.setAdvanced(false);
            gc.drawText("GDI+ (drawTextGDIP):", margin, row2Y + labelH + 8, SWT.DRAW_TRANSPARENT);
            gc.setAdvanced(true);
            for (int i = 0; i < CASES.length; i++) {
                int x = margin + i * colW, y = row2Y + labelH + 24;
                gc.drawText(CASES[i][2], x, y, SWT.DRAW_DELIMITER | SWT.DRAW_TRANSPARENT);
            }
            gc.setAdvanced(false);
            for (int i = 0; i < CASES.length; i++) {
                int x = margin + i * colW, y = row2Y + labelH + 24;
                gc.drawRectangle(x - 2, y - 2, colW - 16, rowH - labelH - 24);
            }
        });

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }
        font.dispose();
        display.dispose();
    }
}

GDI+ Graphics_DrawString only treats \n as a line delimiter; bare \r
is silently ignored rather than treated as a line break. The GDI
DrawText path (non-advanced mode) and the old DrawDriverString path
both accepted \r and \r\n as line delimiters, so the mismatch was a
regression for callers that pass Windows-style (CRLF) or old Mac-style
(CR-only) line endings with DRAW_DELIMITER set.

Fix: in drawTextGDIP(), when DRAW_DELIMITER is active, normalise \r\n
to \n first (to avoid double line breaks), then convert any remaining
lone \r to \n before handing the string to Graphics_DrawString.

Add a cross-platform regression test that verifies all three line-ending
variants (\n, \r, \r\n) produce the same text height in both advanced and
non-advanced rendering mode.

Contributes to eclipse-platform#3091
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 6, 2026

Test Results

  182 files  ±0    182 suites  ±0   25m 28s ⏱️ - 1m 17s
4 731 tests +1  4 708 ✅ +1   23 💤 ±0  0 ❌ ±0 
6 860 runs  +6  6 697 ✅ +6  163 💤 ±0  0 ❌ ±0 

Results for commit 5e3cee1. ± Comparison against base commit c42bbab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant